/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{	
	RecordFile rf;   // RecordFile containing the table
  	RecordId   rid;  // record cursor for table scanning
  	
  	BTreeIndex bTreeIndex;
  
  	RC     rc;
  	int    key;     
  	string value;
  	int    count = 0;
  	int    diff;
  	
  	bool index_Opened = false;

  	// open the table file
  	if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
   		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    	return rc;
  	}
  
  	SelCond* equal_Key = NULL;
	SelCond* equal_Value = NULL;
	
	SelCond* notEqual_Key = NULL;
	SelCond* notEqual_Value = NULL;
  
	SelCond* max_Key = NULL;
	SelCond* max_Value = NULL;
	
	SelCond* min_Key = NULL;
	SelCond* min_Value = NULL;
  
  	int SelCond_Value;
  
  	bool greater_Or_Equal = false;
  	bool less_Or_Equal = false;
  
  	for (int i = 0; i < cond.size(); i++)
  	{
		SelCond_Value = atoi(cond[i].value);
		
		if(cond[i].attr == 1) 
		{
			switch (cond[i].comp)
			{
				case SelCond::EQ:
					equal_Key = (SelCond*) &cond[i];
					break;
				case SelCond::NE:
					notEqual_Key = (SelCond*) &cond[i];
					break;
				case SelCond::LE:
					SelCond_Value++;
				case SelCond::LT:
					if (max_Key == NULL || atoi(max_Key->value) < SelCond_Value)
					{
						max_Key = (SelCond*) &cond[i];
						if(max_Key->comp == SelCond::LE)
						{
							less_Or_Equal = true;
						}
						else
						{
							less_Or_Equal = false;
						}
					}
					break;
				case SelCond::GE:
					SelCond_Value--;
				case SelCond::GT:
					if (min_Key == NULL || atoi(min_Key->value) > SelCond_Value)
					{
						min_Key = (SelCond*) &cond[i];
						if(min_Key->comp == SelCond::GE)
						{
							greater_Or_Equal = true;
						}
						else
						{
							greater_Or_Equal = false;
						}
					}
					break;
			}
		}
		else if(cond[i].attr == 2) 
		{
			switch (cond[i].comp)
			{
				case SelCond::EQ:
					equal_Value = (SelCond*) &cond[i];
					break;
				case SelCond::NE:
					notEqual_Value = (SelCond*) &cond[i];
					break;
				case SelCond::LE:
					SelCond_Value++;
				case SelCond::LT:
					if (max_Value == NULL || atoi(max_Value->value) < SelCond_Value)
						max_Value = (SelCond*) &cond[i];
					break;
				case SelCond::GE:
					SelCond_Value--;
				case SelCond::GT:
					if (min_Value == NULL || atoi(min_Value->value) > SelCond_Value)
						min_Value = (SelCond*) &cond[i];
					break;
			}
			break;
		}
	}
  
 	 if ((bTreeIndex.open(table + ".idx", 'r')) == 0) {
		index_Opened = true;
		
		IndexCursor cursor;
		
		if (equal_Key || min_Key || max_Key || notEqual_Key) {
	
			int key_Current, key_Min, key_Max, key_NotEqual;
			
			if (equal_Key)
			{
				key_Current = atoi(equal_Key->value);
			}
			else if(min_Key)
			{
				key_Min = atoi(min_Key->value);
				key_Current = key_Min;
			}
			
			if(max_Key)
			{
				key_Max = atoi(max_Key->value);
				if(! min_Key)
				{
					key_Current = key_Max;
				}
			}
			
			if(notEqual_Key)
			{
				key_NotEqual = atoi(notEqual_Key->value);
			}
	
			if(max_Key)
			{
				rc = bTreeIndex.locate(0, cursor);
				if(rc != 0)
				{
					bTreeIndex.close();
					return rc;
				}
			}
			else if(! notEqual_Key)
			{
				rc = bTreeIndex.locate(key_Current, cursor);
				if (rc != 0)
				{
					bTreeIndex.close();
					return rc;
				}
			}
					
			while(bTreeIndex.readForward(cursor, key, rid) == 0 )
			{		   			
				if(equal_Key && key != key_Current)
				{
					continue;
				}
				if(min_Key && ((greater_Or_Equal)?(key < key_Min):(key <= key_Min)))
				{
					continue;
				}
				if(max_Key && ((less_Or_Equal)?(key > key_Max):(key >= key_Max)))
				{
					break;
				}
				if(notEqual_Key && key == key_NotEqual)
				{
					continue;
				}
				
				rc = rf.read(rid, key, value);
				if(rc != 0)
				{
					fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
					bTreeIndex.close();
					return rc;
				}
	
				// check the conditions on the tuple
				if(equal_Value != NULL && value != equal_Value->value)
				{
					continue;
				}
				if(max_Value != NULL && value >= max_Value->value)
				{
					continue;
				}
				if(min_Value != NULL && value <= min_Value->value)
				{
					continue;
				}
				if(notEqual_Value && value <= notEqual_Value->value)
				{
					continue;
				}
	
				// the condition is met for the tuple. 
				// increase matching tuple counter
				count++;
	
				// print the tuple 
				switch(attr) 
				{
					case 1:  // SELECT key
					  fprintf(stdout, "%d\n", key);
					  break;
					case 2:  // SELECT value
					  fprintf(stdout, "%s\n", value.c_str());
					  break;
					case 3:  // SELECT *
					  fprintf(stdout, "%d '%s'\n", key, value.c_str());
					  break;
				}
				
				if(equal_Key && key == key_Current)
				{
					break;
				}
				if(notEqual_Key && key != key_NotEqual)
				{
					break;
				}
			}
		}
		
		if(equal_Key || min_Key || max_Key || notEqual_Key) {
			if(attr == 4) {
				fprintf(stdout, "%d\n", count);
			}
		}
		
		if(!(equal_Key || min_Key || max_Key)) {
			bTreeIndex.close();
			goto here;
		}
		
		bTreeIndex.close();
	}
  
	if(!index_Opened)
	{
		// scan the table file from the beginning
		here:
		rid.pid = rid.sid = 0;
		count = 0;
		while(rid < rf.endRid()) {
			// read the tuple
			if ((rc = rf.read(rid, key, value)) < 0) {
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto exit_select;
			}
	
			// check the conditions on the tuple
			if(equal_Key != NULL && (int) key != atoi(equal_Key->value))
			{
				goto next_tuple;
			}
			if(max_Key != NULL && (int) key >= atoi(max_Key->value))
			{
				goto next_tuple;
			}
			if(min_Key != NULL && (int) key <= atoi(min_Key->value))
			{
				goto next_tuple;
			}
			if(notEqual_Key && (int) key == atoi(notEqual_Key->value))
			{
				goto next_tuple;
			}
	
			if(equal_Value != NULL && value != equal_Value->value)
			{
				goto next_tuple;
			}
			if(max_Value != NULL &&  value >= max_Value->value)
			{
				goto next_tuple;
			}
			if(min_Value != NULL && value <= min_Value->value)
			{
				goto next_tuple;
			}
			if(notEqual_Value && value == notEqual_Value->value)
			{
				goto next_tuple;
			}
	
			// the condition is met for the tuple. 
			// increase matching tuple counter
			count++;
	
			// print the tuple 
			switch (attr)
			{
				case 1:  // SELECT key
				  fprintf(stdout, "%d\n", key);
				  break;
				case 2:  // SELECT value
				  fprintf(stdout, "%s\n", value.c_str());
				  break;
				case 3:  // SELECT *
				  fprintf(stdout, "%d '%s'\n", key, value.c_str());
				  break;
			}
	
			// move to the next tuple
			next_tuple:
			++rid;
		}
	
		// print matching tuple count if "select count(*)"
		if (attr == 4) {
		fprintf(stdout, "%d\n", count);
		}
		rc = 0;
	
		// close the table file and return
		exit_select:
		rf.close();
	}
	
	return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	// create loadfile input stream
	ifstream input_Stream;
	input_Stream.open(loadfile.c_str());
	
	// error if loadfile can't be opened
	if(!input_Stream.is_open())
	{
		//cout << "open loadfile failed\n";
		input_Stream.close();
		return RC_FILE_OPEN_FAILED;
	}
	
	// create RecordFile as .tbl file
	RecordFile* rf = new RecordFile(table + ".tbl", 'w');
	
	// create btreeindex
	BTreeIndex b_Tree_Index;
	
	if(index)
	{
		b_Tree_Index.open(table + ".idx", 'w');
	}
	
	// go through each line while getline and input stream is in good state
	string buffer;
	while(getline(input_Stream, buffer) && input_Stream.good())
	{
		//cout << "buffer = " + buffer + "\n";
		int key;
		string value;
		RecordId rID;
		
		// parse the line in buffer into a key and value
		if(parseLoadLine(buffer, key, value) == 0)
		{
			//cout << "parsing\n";
			// append to Record .tbl file
			if(rf->append(key, value, rID) == 0)
			{
				//cout << "appending\n";
				if(index)
				{
					RC rc = b_Tree_Index.insert(key, rID);
					if(rc != 0)
					{
						return RC_FILE_WRITE_FAILED;
					}
				}
			}
			// failed to append to .tbl file
			else
			{
				return RC_FILE_WRITE_FAILED;
			}
		}
		// failed to parse buffer
		else	
		{
			return RC_FILE_READ_FAILED;
		}
	}
	
	// close loadfile input stream, close record file
	//cout << "input stream closed, record file closed\n";
	rf->close();
	delete(rf);
	input_Stream.close();
	b_Tree_Index.close();
	return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
